iT邦幫忙

2022 iThome 鐵人賽

DAY 7
0
自我挑戰組

基於自然語言處理的新聞意見提取應用開發筆記系列 第 7

[Day-07] 讓 AWS Lambda 定時執行 Python 爬蟲程式

  • 分享至 

  • xImage
  •  

Day-07 內容

  • AWS Lambda 服務的運作方式
  • AWS SAM(AWS Serverless Application Model)
    • 什麼是 AWS SAM ?
    • 前置作業與安裝 AWS SAM CLI
  • 建構無伺服器 Python 爬蟲應用程式
    • 從 Hello World Example 開始
    • 應用程式檔案結構
    • 改變檔案內容
    • 建置與部署無伺服器應用

經過了前幾日的奮鬥,要用來儲存網路新聞文章的資料結構(Data Class) 定義好了,運行在 AWS 上的 PostgreSQL 資料庫也準備好了。

那麼,
要開始讓新聞資料源源不斷進來的環節了。/images/emoticon/emoticon34.gif

就如同前幾天的挑戰文章中提到的那樣,我希望這次打造的「基於自然語言處理的新聞意見提取應用」有辦法做到「自動更新獲取最新的新聞文章」,所以一定會需要建立一個機制,能夠在設定的時間自動執行以 Python 撰寫的爬蟲程式。

如果在 Google 上查詢讓 Python 在設定好的時間自動執行的方案,有一些像是用 sleep() 搭配迴圈等待特定時間,或是使用一些排程相關套件的解決辦法。(例如使用Timeloop、sched、schedule、APScheduler等,這部分詳細用法我沒有進一步去了解)

不過以上想法有個很重要的缺點,那就是我沒辦保證包含排程或等待的 Python 程式長久在我個人的電腦上執行。因為除了運行 Python 爬蟲程式外,我還需要用電腦完成其他事務。可能會因為要帶筆電外出,沒有保持網路連線,造成在設定好的時間無法獲得新的新聞資料。又或者電腦因為其他原因發生關機情況,致使 Python 爬蟲程式中斷。

要解決上述情形,我想改在雲端服務的虛擬機執行 Python 爬蟲程式會是個可行的方案。但是虛擬機器的租用價格也有些小貴,難道就沒有更好的解決方案了嗎?

有的!

就是今天要分享的解決方案 –「設定 Amazon EventBridge 規則,定時觸發 AWS Lambda 的 Python 爬蟲程式」。
此方案由於 AWS 本身的免費額度,加上只有運行時計費的特點,會比租用執行個體更經濟實惠。

由於這個方案採用 AWS 的服務架設,會有下面列出的問題需要留意:

  1. AWS Lambda 服務不像一般電腦可以安裝普通的瀏覽器程式(本文章以 google chrome 為例),那要如何使用 Python 的 selenium 套件?

  2. 要如何讓建立的 AWS Lambda 服務可以連線到昨天同樣在 AWS 上建立的 PostgreSQL 資料庫?

以上兩個要解決的課題會安插在接下來的解說當中,如果你也在設法解決相似問題,可以多留意這個部分。那麼要正式開始今天的分享了喔!

提醒: 這篇文章主要在講述「讓 AWS Lambda 定時執行爬蟲程式」的方法與設定,並沒有包含爬蟲程式的實作。


AWS Lambda 服務的運作方式

以下說明參考及部分擷取自 AWS Lambda
AWS Lambda 是一種無伺服器、事件推動的運算服務,可讓您針對幾乎任何類型的應用程式或後端服務執行程式碼,而無需佈建或管理伺服器。您可以從超過 200 個 AWS 服務和軟體即服務 (SaaS) 應用程式觸發 Lambda,且僅需針對所使用的服務付費。

基於可以搭配其他 AWS 服務的特性,Lambda 的用途相當廣泛,可以作為檔案處理、串流處理、Web 應用程式、IoT 後端、行動後端等應用的其中一個環節。除了Python 外還支援 Java、Go、Node.js 等多種程式語言,且可以選擇在 x86_64 或 ARM 架構下執行你的程式。

以 Python 為例,Lambda 服務的核心是使用者定義的 lambda_handler() 這個 function,程式碼結構範例如下所示:

import json

def lambda_handler(event, context):
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

這段程式碼中有幾個重點:

  1. lambda_handler() 需要接受 eventcontext 這兩個參數,分別用來傳入不同的資訊:

  2. 如果你在最後 return 像是上方程式碼中具有 statusCodebody 的結構,將會變成此 Lambda 程式的 response 輸出,後續可以用於 API 建立。當然也可以不 retuern 任何東西。

  3. 如同上面 import json 一樣,Python 的 Lambda 程式可以 import 事先設定好要安裝在此 Lambda 應用執行環境中的套件。


「設定 Amazon EventBridge 規則,定時觸發 AWS Lambda 的 Python 爬蟲程式」這個任務中,主要功能只會使用到 Amazon EventBridge 以及 AWS Lambda 這兩個服務(還會需要一些儲存、管理、監控的次要服務),所以可以採用 AWS SAM(Serverless Application Model)來一次性建立這兩個服務並完成其他所需設定。

當然你可以使用 AWS 提供的網路使用介面建構 Lambda 以及其他服務,並將他們設定為彼此連動。但是相比於在網頁點選按鈕、操作選單、輸入數值來建立需要的 AWS 服務,在這次的情況下使用 AWS SAM 會讓之後變更 Python 程式碼或者 AWS 服務設定更輕鬆,還有著方便在本地端進行測試、建立 GitHub Action 等優點。

接著就介紹一下 AWS SAM ,以及如何使用這個工具建立這次需要的功能。


AWS SAM(AWS Serverless Application Model)

什麼是 AWS SAM ?

下方內容整理自 什麼是AWS Serverless Application Model(AWS SAM)?
AWS Serverless Application Model(AWS SAM)是一種開放原始碼架構,可以用於建置 AWS 無伺服器應用程式上。一個無伺服器應用程式是 Lambda 函數、事件來源及其他資源的組合,以共同執行任務。

AWS SAM 包含下列兩個重要的部分:

  • AWS SAM模板規範

    • 您可以使用此規範定義無服務器應用程序。它為您提供簡單、乾淨的語法來描述函數、API、權限、配置和事件,以組成無伺服器應用程式的函數、API、權限、組態和事件。您可以使用AWS SAM模板文件,以便在單個可部署的版本控制實體(即無服務器應用程序)上進行操作。
  • AWS SAM命令列界面 (AWS SAMCLI)

    • 您可以使用此工具構建無服務器應用程序,這些應用程序由AWS SAM。CLI 提供的命令使您能夠:
      • 驗證AWS SAM模板文件是根據規範編寫的
      • 在本地調用 Lambda 函數
      • 逐步調試 Lambda 函數
      • 打包並將無服務器應用程序部署到AWS雲端
      • 其餘請參考 AWS SAMCLI 命令參考

AWS SAM 優點的詳細說明請見 什麼是AWS Serverless Application Model(AWS SAM)?


前置作業與安裝 AWS SAM CLI

注意:下面的安裝過程將以 macOS 為主,使用其他作業系統請查看安裝 AWS SAM CLI

前置作業

  1. 建立 AWS 帳戶

    • 這步驟已經有帳戶就可以跳過。
  2. 設定AWS Identity and Access Management(IAM) 權限和AWS登入資料

    • 會使用到什麼服務就要新增對應的權限,建議直接在 IAM 進行操作。
  3. 安裝 Docker(視情況安裝,這次要建立的應用需要)

  4. 設定AWS證書

    1. 依照 Installing or updating the latest version of the AWS CLI 中的教學安裝 AWS CLI。
    2. 根據 Configuration basics 頁面中的 "Access key ID and secret access key" 段落內容,取得 Access key IDSecret access key
    3. 在終端機中執行下方指令,並完成資訊填寫(可選則 us-east-1 以外的地區):
      $ aws configure
      AWS Access Key ID [None]: Access key ID
      AWS Secret Access Key [None]: Secret access key
      Default region name [None]: us-east-1 
      Default output format [None]: 
      

安裝 AWS SAM CLI

  1. 安裝 Homebrew

  2. 安裝 AWS SAM CLI

    • 在安裝過 Homebrew 的終端機環境下,執行下列指令:
      brew tap aws/tap
      brew install aws-sam-cli
      sam --version
      
    • 如正確安裝會顯示類似 SAM CLI, version 1.35.0 的輸出。

建構無伺服器 Python 爬蟲應用程式

此次採用下列設置,以 AWS SAM 建構無伺服器 Python 爬蟲應用程式:

  • 程式語言及版本: Python 3.9
  • package type: Image
    • 這裡選用 image 會搭配 Docker 做使用(是為了解決瀏覽器安裝問題)
    • 一般如果沒有特殊需求,選用 zip 就可以了。

從 Hello World Example 開始

在終端機執行 sam init,並依照下方所示進行選擇:

❯ sam init

You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.

Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1

Choose an AWS Quick Start application template
        1 - Hello World Example
        2 - Multi-step workflow
        3 - Serverless API
        4 - Scheduled task
        5 - Standalone function
        6 - Data processing
        7 - Infrastructure event management
        8 - Lambda EFS example
        9 - Machine Learning
Template: 1

Use the most popular runtime and package type? (Python and zip) [y/N]: N

Which runtime would you like to use?
        1 - dotnet6
        2 - dotnet5.0
        3 - dotnetcore3.1
        4 - go1.x
        5 - graalvm.java11 (provided.al2)
        6 - graalvm.java17 (provided.al2)
        7 - java11
        8 - java8.al2
        9 - java8
        10 - nodejs16.x
        11 - nodejs14.x
        12 - nodejs12.x
        13 - python3.9
        14 - python3.8
        15 - python3.7
        16 - python3.6
        17 - ruby2.7
        18 - rust (provided.al2)
Runtime: 13

What package type would you like to use?
        1 - Zip
        2 - Image
Package type: 2

Based on your selections, the only dependency manager available is pip.
We will proceed copying the template using pip.

Would you like to enable X-Ray tracing on the function(s) in your application?  [y/N]: N

Project name [sam-app]:

注意:Project name [sam-app]: 可以按 enter 使用預設的 sam-app 或是自行命名。

完成後可以看到類似下方的輸出結果:

Cloning from https://github.com/aws/aws-sam-cli-app-templates (process may take a moment)

    -----------------------
    Generating application:
    -----------------------
    Name: sam-app
    Base Image: amazon/python3.9-base
    Architectures: x86_64
    Dependency Manager: pip
    Output Directory: .

    Next steps can be found in the README file at ./sam-app/README.md
    

    Commands you can use next
    =========================
    [*] Create pipeline: cd sam-app && sam pipeline init --bootstrap
    [*] Validate SAM template: sam validate
    [*] Test Function in the Cloud: sam sync --stack-name {stack-name} --watch
    

SAM CLI update available (1.57.0); (1.55.0 installed)
To download: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html

應用程式檔案結構

現在有了基本的應用程式檔案架構,長得像這樣:

❯ tree
.
└── sam-app
    ├── README.md
    ├── __init__.py
    ├── events
    │   └── event.json
    ├── hello_world
    │   ├── Dockerfile
    │   ├── __init__.py
    │   ├── app.py
    │   └── requirements.txt
    ├── template.yaml
    └── tests
        ├── __init__.py
        └── unit
            ├── __init__.py
            └── test_handler.py

5 directories, 11 files

當中有幾個重要的部分(當中 template.yaml 最為重要):

  • events 資料夾

    • 當中包含 event.json,是在執行 sam local invoke HelloWorldFunction --event events/event.json 進行本地端測試時會作為 lambda_handler(event, context) 中的 event 輸入。(注意:HelloWorldFunction 會因為 template.yaml 中的定義而有所不同)
  • hello_world 資料夾

    • 主要用來放無伺服器應用的程式碼,lambda_handler(event, context) 包含在 app.py 當中,requirements.txt 內記錄了需要安裝的 Python 套件資訊,Dockerfile 包含產生 Image 的流程,會在過程中安裝 requirements.txt 所記錄的項目(注意:hello_world 這個資料夾名稱會因為 template.yaml 中的定義而有所不同)
  • tests 資料夾

    • pytest 所需的測試檔案結構。
  • template.yaml

    • 為 AWS SAM 的模板,嚴格遵循 AWS CloudFormation,但不具備所有 AWS CloudFormation 可做的設定。決定將要部署的資源以及其設定,sam buildsam deploy 皆會依照 template.yaml 中的設定進行操作。
    • 下方是一個 template.yaml 的範本片段,每個區塊的設定內容都可以在 AWS Serverless Application Model(AWS SAM) 規格 中找到相關說明。
      Transform: AWS::Serverless-2016-10-31
      
      Globals:
        set of globals
      
      Description:
        String
      
      Metadata:
        template metadata
      
      Parameters:
        set of parameters
      
      Mappings:
        set of mappings
      
      Conditions:
        set of conditions
      
      Resources:
        set of resources
      
      Outputs:
        set of outputs
      
      

改變檔案內容

eventstests 的用途暫時不會碰到,所以先示範如何更動 hello_world 資料夾內的檔案和 template.yaml

  • hello_world 資料夾內的檔案 => 關係到 Lambda 函數的功能
  • template.yaml => 關係到 build 流程與 deploy 所需資源及設定

改變 template.yaml 中的設定

改變 template.yaml 中的設定如下:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  python3.9

  Sample SAM Template for sam-app

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 300

Resources: 
  NewsCollectFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Metadata:
      Dockerfile: Dockerfile
      DockerContext: ./hello_world
      DockerTag: python3.9-v1
    Properties:
      PackageType: Image
      Architectures:
        - x86_64
      MemorySize: 2048
      EphemeralStorage:
        Size: 2048
      VpcConfig:
        SecurityGroupIds:
          - sg-XXXXXXX
        SubnetIds:
          - subnet-XXXXXXX
      Events:
        ScheduleChinatimes:
          Type: Schedule
          Properties:
              Name: ScheduleChinatimes
              Description: Trigger AWS Lambda function to collect Chinatimes news every hour.
              Schedule: cron(0 * ? * * *)
              Input: "{\"media\":\"chinatimes\"}"
              Enabled: True

這裡先針對上方的內容作個說明:

  • Globals:
    • Function:
      • Timeout: 300 :這個 template.yaml 中紀錄的 Lambda 應用最長執行時間 (s)
  • NewsCollectFunction: 部署(deploy)實在 AWS Lambda 使用的名稱
    • Type: AWS::Serverless::Function:類型是 AWS Lambda 應用
      • Metadata:
        • Dockerfile: Dockerfile 的檔案名稱
        • DockerContext: Dockerfile 所存放資料夾的路徑
      • Properties:
        • PackageType: Image ,使用 Image 而不是 zip
        • MemorySize: 2048,AWS Lambda 應用程式執行時可用的最大記憶體容量
        • EphemeralStorage:
          • Size: 2048,AWS Lambda 應用程式的儲存容量限制
        • VpcConfig: 下方另外獨立說明

解決問題:要如何讓建立的 AWS Lambda 服務可以連線到昨天同樣在 AWS 上建立的 PostgreSQL 資料庫?
VpcConfig 的 VPC 設定成跟昨天建立的 PostgreSQL 一樣,就可以解決 VPC 連線權限的問題。
在昨天提到的 AWS RDS > 資料庫中,點選間裡的資料庫 instance,取得「VPC 安全群組」和「子網路」,並且填入下方結構中。

-  VpcConfig: 
    - SecurityGroupIds:
      - sg-XXXXXXX
    - SubnetIds:
      - subnet-XXXXXXX
      - subnet-XXXXXXX
  • Events: 包含會觸發 Lambda 應用的事件設定
    • ScheduleChinatimes: 自訂的事件名稱
      • Type: Schedule,表示選用 Amazon EventBridge 排成作為觸發源。
      • Properties:
        • Name: ScheduleChinatimes,自訂的事件名稱
        • Schedule: cron(0 * ? * * *) ,排程規則(這裡設定一小時觸發一次,設定方法請見Creating an Amazon EventBridge rule that runs on a schedule
        • Input: "{\"media\":\"chinatimes\"}",排程觸發時 lambda_handler 的 event 輸入。
        • Enabled: True,是否啟用排程

補充:

如果想自行更改 template.yaml 中的設定,在 AWS Serverless Application Model(AWS SAM) 規格 可以找到相關說明


改變 Dockerfile 中的設定

解決問題:AWS Lambda 服務不像一般電腦可以安裝普通的瀏覽器程式(本文章以 google chrome 為例),那要如何使用 Python 的 selenium 套件?

由於 Python 所撰寫的爬蟲功能會用到 selenium 套件,所以我們需要安裝 Chrome。
我參考 docker-selenium-lambda 改寫

Dockerfile,內容如下:

FROM public.ecr.aws/lambda/python@sha256:ce32e747dd996dc402c3a68ac5ce3ac116ab470c56b1275907a262137f92a52d as build
RUN yum install -y unzip && \
    curl -Lo "/tmp/chromedriver.zip" "https://chromedriver.storage.googleapis.com/104.0.5112.79/chromedriver_linux64.zip" && \
    curl -Lo "/tmp/chrome-linux.zip" "https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F1012728%2Fchrome-linux.zip?alt=media" && \
    unzip /tmp/chromedriver.zip -d /opt/ && \
    unzip /tmp/chrome-linux.zip -d /opt/

FROM public.ecr.aws/lambda/python@sha256:ce32e747dd996dc402c3a68ac5ce3ac116ab470c56b1275907a262137f92a52d
RUN yum install atk cups-libs gtk3 libXcomposite alsa-lib \
    libXcursor libXdamage libXext libXi libXrandr libXScrnSaver \
    libXtst pango at-spi2-atk libXt xorg-x11-server-Xvfb \
    xorg-x11-xauth dbus-glib dbus-glib-devel -y
RUN pip install selenium
COPY --from=build /opt/chrome-linux /opt/chrome
COPY --from=build /opt/chromedriver /opt/

COPY requirements.txt ./
RUN pip3 install -r requirements.txt
COPY app.py ./

CMD ["app.lambda_handler"]

app.pyrequirements.txt 的範例

下面是一個用 selenium 獲取網路資料的程式碼範例,之後可以依自己的使用需求做更動

  • requirements.txt 範例如下(將之後會用到的套件都紀錄在這個檔案裡):

    selenium
    
  • app.py 範例如下:

    import json
    from selenium import webdriver
    from tempfile import mkdtemp
    from selenium.webdriver.common.by import By
    
    
    def lambda_handler(event, context):
    
        options = webdriver.ChromeOptions()
        options.binary_location = '/opt/chrome/chrome'
        options.add_argument('--headless')
        options.add_argument('--no-sandbox')
        options.add_argument("--disable-gpu")
        options.add_argument("--window-size=1280x1696")
        options.add_argument("--single-process")
        options.add_argument("--disable-dev-shm-usage")
        options.add_argument("--disable-dev-tools")
        options.add_argument("--no-zygote")
        options.add_argument(f"--user-data-dir={mkdtemp()}")
        options.add_argument(f"--data-path={mkdtemp()}")
        options.add_argument(f"--disk-cache-dir={mkdtemp()}")
        options.add_argument("--remote-debugging-port=9222")
        chrome = webdriver.Chrome("/opt/chromedriver",
                                  options=options)
        chrome.get("https://example.com/")
        return {
            "statusCode": 200,
            "body": json.dumps(
                {
                    "message": f"{chrome.find_element(by=By.XPATH, value="//html").text}",
                }
            ),
        }
    

建置與部署無伺服器應用

建置

在有 template.yaml 的路經執行 sam build

部署

在有 template.yaml 的路經執行 sam deploy --guided,除了 HelloWorldFunction may not have authorization defined, Is this okay? [y/N] 要選 Y 以外,其他選項按 enter 使用預設值。

到這裡就完成範例程式的部署了,可以從網頁版的 AWS Lambda 頁面中查看這次範例應用的執行結果。(template.yaml 的範例設定一小時執行一次)
/images/emoticon/emoticon42.gif


寫的有些匆忙,如果文章有錯誤,歡迎指正~
/images/emoticon/emoticon41.gif


上一篇
[Day-06] 以 PostgreSQL 建立新聞資料庫(採用 AWS Amazon Aurora Serverless v2)
下一篇
[Day-08] 以 AWS Lambda 和 API Gateway 建立新聞資料 API
系列文
基於自然語言處理的新聞意見提取應用開發筆記17
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言